1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   */
19  package groovy;
20  
21  import groovy.lang.Closure;
22  import groovy.lang.Reference;
23  import junit.framework.TestCase;
24  import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
25  
26  import java.math.BigDecimal;
27  import java.math.BigInteger;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Collection;
31  import java.util.HashMap;
32  import java.util.LinkedHashMap;
33  import java.util.List;
34  import java.util.Map;
35  
36  import static org.codehaus.groovy.runtime.DefaultGroovyMethods.any;
37  import static org.codehaus.groovy.runtime.DefaultGroovyMethods.collect;
38  import static org.codehaus.groovy.runtime.DefaultGroovyMethods.each;
39  import static org.codehaus.groovy.runtime.DefaultGroovyMethods.every;
40  import static org.codehaus.groovy.runtime.DefaultGroovyMethods.findAll;
41  import static org.codehaus.groovy.runtime.DefaultGroovyMethods.inject;
42  import static org.codehaus.groovy.runtime.DefaultGroovyMethods.join;
43  import static org.codehaus.groovy.runtime.DefaultGroovyMethods.max;
44  import static org.codehaus.groovy.runtime.DefaultGroovyMethods.sort;
45  
46  /**
47   * Groovy's Closure class isn't specifically designed with Java integration in
48   * mind, but these tests illustrate some of the possible ways to use them from Java.
49   */
50  public class ClosureJavaIntegrationTest extends TestCase {
51      Map<String, Integer> zoo = new LinkedHashMap<String, Integer>();
52      List<String> animals = Arrays.asList("ant", "bear", "camel");
53  
54      @Override
55      protected void setUp() throws Exception {
56          super.setUp();
57          zoo.put("Monkeys", 3);
58          zoo.put("Giraffe", 2);
59          zoo.put("Lions", 5);
60      }
61  
62      public void testJoinListNonClosureCase() {
63          assertEquals(join(animals, ", "), "ant, bear, camel");
64      }
65  
66      public void testEachList() {
67          final List<Integer> result = new ArrayList<Integer>();
68          each(animals, new Closure(null) {
69              public void doCall(String arg) {
70                  result.add(arg.length());
71              }
72          });
73          assertEquals(Arrays.asList(3, 4, 5), result);
74      }
75  
76      public void testEachMap() {
77          final List<String> result = new ArrayList<String>();
78          each(zoo, new Closure(null) {
79              public void doCall(String k, Integer v) {
80                  result.add("k=" + k + ",v=" + v);
81              }
82          });
83          assertEquals(Arrays.asList("k=Monkeys,v=3", "k=Giraffe,v=2", "k=Lions,v=5" ), result);
84      }
85  
86      public void testCollectList() {
87          assertEquals(Arrays.asList(3, 4, 5), collect(animals, new Closure<Integer>(null) {
88              public Integer doCall(String it) {
89                  return it.length();
90              }
91          }));
92      }
93  
94      public void testMaxMap() {
95          Map.Entry<String, Integer> lionEntry = null;
96          for (Map.Entry<String, Integer> entry : zoo.entrySet()) {
97              if (entry.getKey().equals("Lions")) lionEntry = entry;
98          }
99          assertEquals(lionEntry, max(zoo.entrySet(), new Closure<Integer>(null) {
100             public Integer doCall(Map.Entry<String, Integer> e) {
101                 return e.getKey().length() * e.getValue();
102             }
103         }));
104     }
105 
106     public void testSortMapKeys() {
107         assertEquals(Arrays.asList("Monkeys", "Lions", "Giraffe"), sort(zoo.keySet(), new Closure<Integer>(null) {
108             public Integer doCall(String a, String b) {
109                 return -a.compareTo(b);
110             }
111         }));
112         assertEquals(Arrays.asList("Giraffe", "Lions", "Monkeys"), sort(zoo.keySet(), new Closure<Integer>(null) {
113             public Integer doCall(String a, String b) {
114                 return a.compareTo(b);
115             }
116         }));
117     }
118 
119     public void testAnyMap() {
120         assertTrue(any(zoo, new Closure<Boolean>(null) {
121             public Boolean doCall(String k, Integer v) {
122                 return k.equals("Lions") && v == 5;
123             }
124         }));
125     }
126 
127     public void testFindAllAndCurry() {
128         Map<String, Integer> expected = new HashMap<String, Integer>(zoo);
129         expected.remove("Lions");
130         Closure<Boolean> keyBiggerThan = new Closure<Boolean>(null) {
131             public Boolean doCall(Map.Entry<String, Integer> e, Integer size) {
132                 return e.getKey().length() > size;
133             }
134         };
135         Closure<Boolean> keyBiggerThan6 = keyBiggerThan.rcurry(6);
136         assertEquals(expected, findAll(zoo, keyBiggerThan6));
137     }
138 
139     public void testListArithmetic() {
140         List<List> numLists = new ArrayList<List>();
141         numLists.add(Arrays.asList(1, 2, 3));
142         numLists.add(Arrays.asList(10, 20, 30));
143         assertEquals(Arrays.asList(6, 60), collect(numLists, new Closure<Integer>(null) {
144             public Integer doCall(Integer a, Integer b, Integer c) {
145                 return a + b + c;
146             }
147         }));
148         Closure<Integer> arithmeticClosure = new Closure<Integer>(null) {
149             public Integer doCall(Integer a, Integer b, Integer c) {
150                 return a * b + c;
151             }
152         };
153         Closure<Integer> tensAndUnits = arithmeticClosure.curry(10);
154         assertEquals(35, (int) tensAndUnits.call(3, 5));
155         tensAndUnits = arithmeticClosure.ncurry(0, 10);
156         assertEquals(35, (int) tensAndUnits.call(3, 5));
157         tensAndUnits = arithmeticClosure.ncurry(1, 10);
158         assertEquals(35, (int) tensAndUnits.call(3, 5));
159         Closure<Integer> timesPlus5 = arithmeticClosure.rcurry(5);
160         assertEquals(35, (int) timesPlus5.call(15, 2));
161         timesPlus5 = arithmeticClosure.ncurry(2, 5);
162         assertEquals(35, (int) timesPlus5.call(15, 2));
163     }
164 
165     public void testComposition() {
166         Closure<String> toUpperCase = new Closure<String>(null) {
167             public String doCall(String s) {
168                 return s.toUpperCase();
169             }
170         };
171         Closure<Boolean> hasCapitalA = new Closure<Boolean>(null) {
172             public Boolean doCall(String s) {
173                 return s.contains("A");
174             }
175         };
176         Closure<Boolean> hasA = toUpperCase.rightShift(hasCapitalA);
177         assertTrue(every(animals, hasA));
178         Closure<Boolean> alsoHasA = hasCapitalA.leftShift(toUpperCase);
179         assertTrue(every(animals, alsoHasA));
180     }
181 
182     public void testTrampoline() {
183         final Reference<Closure<BigInteger>> ref = new Reference<Closure<BigInteger>>();
184         ref.set(new Closure<BigInteger>(null) {
185             public Object doCall(Integer n, BigInteger total) {
186                 return n > 1 ? ref.get().trampoline(n - 1, total.multiply(BigInteger.valueOf(n))) : total;
187             }
188         }.trampoline());
189         Closure<BigInteger> factorial = new Closure<BigInteger>(null) {
190             public BigInteger doCall(Integer n) {
191                 return ref.get().call(n, BigInteger.ONE);
192             }
193         };
194         assertEquals(BigInteger.valueOf(479001600), factorial.call(12));
195     }
196 
197     public void testInject() {
198         Collection<Integer> c = Arrays.asList(2, 4, 5, 20);
199         Number initial = BigDecimal.ZERO;
200         Closure<? extends Number> closure = new Closure<BigDecimal>(c) {
201             BigDecimal doCall(BigDecimal total, Integer next) {
202                 return total.add(BigDecimal.ONE.divide(new BigDecimal(next)));
203             }
204         };
205         assertTrue(DefaultTypeTransformation.compareEqual(BigDecimal.ONE, inject(c, initial, closure)));
206     }
207 }